﻿Imports QueueManager.Helper
Imports QueueManager.Helper.CRM
Imports QueueManager.Service.Config
Imports System.Text
Imports System.IO
Imports System.Xml
Imports System.Net.Mail
Imports System.Text.RegularExpressions

Public Class QueueMonitor

    Private _objCRM As New CRMHelper
    Private _objRegEx As Regex
    Private _objParser As Regex

    ''' <summary>
    ''' Processes a queue and creates cases
    ''' </summary>
    ''' <param name="QueueSettings">The settings of the queue to process.</param>
    Public Sub ProcessQueue(ByVal QueueSettings As QueueSetting)

        Dim objContactID As Guid
        Dim objContact As contact
        Dim objIncomingEnt As IncomingEntity
        Dim objQueue As queue
        Dim objQueueOwner As systemuser
        Dim objQueueItems As New QueryExpressionHelper(EntityName.queueitem)
        Dim objLink As LinkEntityHelper
        Dim objContactQuery As QueryExpressionHelper
        Dim objQueueResult As BusinessEntity()
        Dim objEntities As BusinessEntityCollection
        Dim objEmail As email
        Dim strUniqueID As String
        Dim strUrl As String
        Dim objName As NameStruct

        Try

            'Query the queue for queue items and retrieve the columns neccessary
            objQueueItems.Columns.AddColumns("title", "sender", "objectid", "enteredon")

            'Filter on the queue id and object type 4202 (EntityName.email)
            objQueueItems.Criteria.FilterOperator = LogicalOperator.And
            objQueueItems.Criteria.Conditions.AddCondition("queueid", ConditionOperator.Equal, QueueSettings.QueueID)
            objQueueItems.Criteria.Conditions.AddCondition("objecttypecode", ConditionOperator.Equal, 4202)

            'Add email as a linked entity and add a condition to only process e-mail with nothing in the regarding field.
            'That should be all new e-mail. 
            If Not QueueSettings.RemoveRepliesFromQueue Then
                objLink = objQueueItems.LinkEntities.AddLink("objectid", "email", "activityid")
                objLink.Criteria.FilterOperator = LogicalOperator.And
                objLink.Criteria.Conditions.AddCondition("regardingobjectid", ConditionOperator.Null, Nothing)
            End If

            LogHelper.Output("Querying queue '{0}'...", QueueSettings.Name)

            'Execute the query
            objQueueResult = _objCRM.GetCrmService.RetrieveMultiple(objQueueItems.Query).BusinessEntities

            LogHelper.Output("Found {0} e-mail on queue.", objQueueResult.Length.ToString)

            'We need the queue e-mail address and owner
            If objQueueResult.Length > 0 Then
                objQueue = CType(_objCRM.GetSingleData(EntityName.queue.ToString, New Guid(QueueSettings.QueueID), New String() {"primaryuserid", "emailaddress", "name"}), queue)
                If objQueue Is Nothing Then
                    LogHelper.Log(String.Format("Can't find queue specified in '{0}'.", QueueSettings.Name), Nothing, LogHelper.LogType.Warning)
                    Exit Sub
                End If
                'Also get the owner e-mail address to be used for notification.
                objQueueOwner = CType(_objCRM.GetSingleData(EntityName.systemuser.ToString, objQueue.primaryuserid.Value, New String() {"internalemailaddress"}), systemuser)
            End If

            'Loop through result
            For Each objItem As queueitem In objQueueResult

                LogHelper.Output("", False)

                'Retrieve the e-mail
                Try
                    LogHelper.Output("Retrieving e-mail '{0}'...", objItem.title)
                    objEmail = CType(_objCRM.GetSingleData(EntityName.email.ToString, objItem.objectid.Value, New String() {"from", "to", "subject", "submittedby", "description", "regardingobjectid"}), email)
                Catch ex As Exception
                    'We can carry on after this. An bug in RTM of 4.0 could leave queueitems linger around without an e-mail attached to it.
                    'May 15th 2008: There is now a hotfix available that corrects this: See http://support.microsoft.com/kb/948172
                    LogHelper.Log("Error retrieving email from queue item.", ex, LogHelper.LogType.Exception)
                End Try

                'Process list of ignore words
                If objEmail IsNot Nothing AndAlso Not String.IsNullOrEmpty(QueueSettings.IgnoreList) Then
                    objEmail = MatchIgnoredWords(objEmail, QueueSettings)
                End If

                'Case number check
                If objEmail IsNot Nothing AndAlso QueueSettings.MatchCaseTicketNumber AndAlso objEmail.regardingobjectid Is Nothing Then
                    'Try and get the case if there's a match for the case number in the title.
                    'Wild attempt to overcome the fact that people get confused with tracking numbers/case numbers
                    objEmail = MatchCaseString(objEmail)
                End If

                'Optionally remove the e-mail from the queue if the e-mail already is related
                'to a case
                If objEmail IsNot Nothing AndAlso QueueSettings.RemoveRepliesFromQueue AndAlso objEmail.regardingobjectid IsNot Nothing Then

                    'Detach from the queue...
                    DetachFromQueue(objEmail, objQueue.queueid.Value.ToString)

                    '...and set to nothing so we don't continue
                    objEmail = Nothing
                End If

                'Check and make sure we are on to To-line in the e-mail. 
                If objEmail IsNot Nothing AndAlso QueueSettings.AllowOnlyTo AndAlso Not String.IsNullOrEmpty(objQueue.emailaddress) Then
                    objEmail = MatchAllowOnlyTo(objEmail, objQueue, QueueSettings)
                End If

                If objEmail IsNot Nothing Then

                    'If partyid is nothing then the e-mail isn't related to a contact
                    If objEmail.from(0).partyid Is Nothing Then

                        LogHelper.Output("No contact found. Trying to match using alternative e-mail address.")

                        objIncomingEnt = New IncomingEntity

                        'New query to look up contact
                        objContactQuery = New QueryExpressionHelper(EntityName.contact.ToString)

                        'Retrieve contact id
                        objContactQuery.Columns.AddColumns("contactid", "firstname", "lastname", "parentcustomerid")

                        'Attempt to lookup contact using email1,email2,email3 (ORed)
                        objContactQuery.Criteria.FilterOperator = LogicalOperator.Or
                        objContactQuery.Criteria.Conditions.AddCondition("emailaddress1", ConditionOperator.Equal, objEmail.from(0).addressused)
                        objContactQuery.Criteria.Conditions.AddCondition("emailaddress2", ConditionOperator.Equal, objEmail.from(0).addressused)
                        objContactQuery.Criteria.Conditions.AddCondition("emailaddress3", ConditionOperator.Equal, objEmail.from(0).addressused)

                        Try
                            objEntities = _objCRM.GetCrmService.RetrieveMultiple(objContactQuery.Query)
                        Catch ex As Exception
                            LogHelper.Log("Error retrieving contacts.", ex, LogHelper.LogType.Exception)
                        End Try

                        If objEntities IsNot Nothing AndAlso objEntities.BusinessEntities.Length > 0 Then

                            LogHelper.Output("Found contact.")

                            'Get the the contact
                            objContact = CType(objEntities.BusinessEntities(0), contact)

                            objIncomingEnt.FirstName = objContact.firstname
                            objIncomingEnt.LastName = objContact.lastname
                            objIncomingEnt.ID = objContact.contactid.Value
                            objIncomingEnt.Type = EntityName.contact.ToString

                            'Check to see if parent account options are alright
                            If Not SetCaseCustomer(objContact, objIncomingEnt, QueueSettings) Then
                                LogHelper.Log(String.Format("Customer with e-mail address '{0}' is missing a parent account and no case will be created for the incoming e-mail.", objEmail.from(0).addressused), Nothing, LogHelper.LogType.Warning)
                                'Set the entity to nothing to skip it
                                objIncomingEnt = Nothing
                            End If

                            'If more than 1 contact returned then log as a warning
                            If objEntities.BusinessEntities.Length > 1 Then
                                LogHelper.Log(String.Format("Duplicates detected. More than one contact was found with the e-mail address {0}. The first one was used.", objEmail.from(0).addressused), Nothing, LogHelper.LogType.Warning)
                            End If
                        End If
                    Else

                        'CRM only supports accounts/contacts/systemusers for cases
                        If objEmail.from(0).partyid.type = EntityName.contact.ToString Then
                            LogHelper.Output("Retrieving contact...")

                            'Retrieve the contact, we need its details when parsing e-mail
                            Try

                                objContact = CType(_objCRM.GetSingleData(EntityName.contact.ToString, _
                                                                         objEmail.from(0).partyid.Value, _
                                                                         New String() {"firstname", "lastname", "parentcustomerid"}), contact)

                                'Create a generic representation of the type used for parsing etc
                                objIncomingEnt = New IncomingEntity
                                objIncomingEnt.FirstName = objContact.firstname
                                objIncomingEnt.LastName = objContact.lastname
                                objIncomingEnt.ID = objEmail.from(0).partyid.Value
                                objIncomingEnt.Type = EntityName.contact.ToString

                                'Check to see if parent account options are alright
                                If Not SetCaseCustomer(objContact, objIncomingEnt, QueueSettings) Then
                                    LogHelper.Log(String.Format("Customer with e-mail address '{0}' is missing a parent account and no case will be created for the incoming e-mail.", objEmail.from(0).addressused), Nothing, LogHelper.LogType.Warning)
                                    'Set the entity to nothing to skip it
                                    objIncomingEnt = Nothing
                                End If

                            Catch ex As Exception
                                'Allow this to happen, contacts might not exist if the contact has been removed and
                                'the queue item still exists.
                                objIncomingEnt = Nothing
                                LogHelper.Log(String.Format("Could not retrieve contact related to e-mail '{0}'.", objEmail.subject), ex, LogHelper.LogType.Exception)
                            End Try

                        ElseIf objEmail.from(0).partyid.type = EntityName.account.ToString Then

                            LogHelper.Output("Retrieving account...")

                            objName = _objCRM.ResolveNames(objEmail.submittedby)

                            'Create a generic representation of the type used for parsing etc
                            objIncomingEnt = New IncomingEntity
                            objIncomingEnt.FirstName = objName.FirstName
                            objIncomingEnt.LastName = objName.LastName
                            objIncomingEnt.ID = objEmail.from(0).partyid.Value
                            objIncomingEnt.Type = EntityName.account.ToString
                            objIncomingEnt.CaseEntityID = objIncomingEnt.ID
                            objIncomingEnt.CaseEntityType = objIncomingEnt.Type

                        ElseIf objEmail.from(0).partyid.type = EntityName.systemuser.ToString Then

                            LogHelper.Output("e-mail is sent by an internal user. Attempting to retrieve a contact...")

                            objContactQuery = New QueryExpressionHelper(EntityName.contact.ToString)

                            'Retrieve contact id
                            objContactQuery.Columns.AddColumns("contactid", "firstname", "lastname")

                            'Attempt to lookup contact using email1,email2,email3 (ORed)
                            objContactQuery.Criteria.FilterOperator = LogicalOperator.Or
                            objContactQuery.Criteria.Conditions.AddCondition("emailaddress1", ConditionOperator.Equal, objEmail.from(0).addressused)
                            objContactQuery.Criteria.Conditions.AddCondition("emailaddress2", ConditionOperator.Equal, objEmail.from(0).addressused)
                            objContactQuery.Criteria.Conditions.AddCondition("emailaddress3", ConditionOperator.Equal, objEmail.from(0).addressused)

                            Try
                                objEntities = _objCRM.GetCrmService.RetrieveMultiple(objContactQuery.Query)
                            Catch ex As Exception
                                LogHelper.Log("Error retrieving contacts.", ex, LogHelper.LogType.Exception)
                            End Try

                            If objEntities IsNot Nothing AndAlso objEntities.BusinessEntities.Length > 0 Then

                                LogHelper.Output("Found contact. Will create case for this contact instead.")

                                'Get the the contact
                                objContact = CType(objEntities.BusinessEntities(0), contact)

                                'Create a generic representation of the type used for parsing etc
                                objIncomingEnt = New IncomingEntity
                                objIncomingEnt.FirstName = objContact.firstname
                                objIncomingEnt.LastName = objContact.lastname
                                objIncomingEnt.ID = objEmail.from(0).partyid.Value
                                objIncomingEnt.Type = EntityName.systemuser.ToString
                                objIncomingEnt.CaseEntityID = objContact.contactid.Value
                                objIncomingEnt.CaseEntityType = EntityName.contact.ToString
                            Else
                                objIncomingEnt = New IncomingEntity
                                objIncomingEnt.Type = "other"
                            End If

                        Else
                            objIncomingEnt = New IncomingEntity
                            objIncomingEnt.Type = "other"
                            LogHelper.Output("Can't create case for sender type '{0}'.", objEmail.from(0).partyid.type)
                        End If
                    End If

                    'Either the sender as been resolved and we can carry on 
                    'or we need to create it
                    If objIncomingEnt IsNot Nothing Then
                        If objIncomingEnt.Type = EntityName.contact.ToString OrElse objIncomingEnt.Type = EntityName.account.ToString OrElse objIncomingEnt.Type = EntityName.systemuser.ToString Then

                            'Only create cases for contact/accounts
                            'We now have a customer so let's create a new case/incident
                            CreateCase(objIncomingEnt, objEmail, QueueSettings, objQueue, objQueueOwner)
                        Else

                            If objIncomingEnt.Type = "" Then
                                'Should we send verification e-mail?
                                If QueueSettings.SendVerificationMail Then

                                    LogHelper.Output("Sending verification e-mail...")

                                    'Route the email to the temp queue
                                    _objCRM.RouteEMail(objEmail, QueueSettings.QueueID, QueueSettings.TempQueueID)

                                    'Create a unique identifier that we can use to retrieve the mail again.
                                    strUniqueID = Now.Ticks.ToString

                                    'Store this in the trackingtoken attribute
                                    objEmail.trackingtoken = strUniqueID

                                    _objCRM.GetCrmService.Update(objEmail)

                                    'The Url sent out consists of "key" (the token), "s" source queue and "t" target queue
                                    'needed in order to move it back once updated.
                                    strUrl = String.Format("{0}?key={1}&s={2}&t={3}", QueueSettings.VerificationMailUrl, strUniqueID, QueueSettings.TempQueueID.ToLower, QueueSettings.QueueID.ToLower)

                                    'Send an e-mail
                                    _objCRM.SendNetMail(QueueSettings.VerificationMailSubject, _
                                                        IO.File.ReadAllText(QueueSettings.VerificationMailTemplate).Replace("{url}", strUrl), _
                                                        objEmail.from(0).addressused, _
                                                        QueueSettings.VerificationMailBcc, _
                                                        CType(IIf(QueueSettings.VerificationMailFrom = "*", objQueueOwner.internalemailaddress, QueueSettings.VerificationMailFrom), String), _
                                                        QueueSettings.VerificationMailUseHtml)

                                    LogHelper.Output("Verification e-mail sent.")

                                    'Or should we simply create the contact?
                                ElseIf QueueSettings.CreateContact Then

                                    LogHelper.Output("Automatically creating contact...")

                                    'Create a new contact using the information provided
                                    objContact = New contact

                                    'Get the name from the e-mail info. Use submitted by if it exists as the full name of 
                                    'the sender might be there. If not, use e-mail address.
                                    If objEmail.submittedby = "" Then
                                        objName = _objCRM.ResolveNames(objEmail.from(0).addressused)
                                    Else
                                        objName = _objCRM.ResolveNames(objEmail.submittedby)
                                    End If

                                    'Set properties
                                    objContact.firstname = objName.FirstName
                                    objContact.lastname = objName.LastName
                                    objContact.emailaddress1 = objEmail.from(0).addressused

                                    'Attempt to resolve the parent account automatically
                                    If QueueSettings.ResolveParent Then
                                        _objCRM.ResolveParentAccount(objContact, _
                                                                     objEmail.from(0).addressused, _
                                                                     QueueSettings.ResolveParentNotify, _
                                                                     QueueSettings.ResolveParentFrom)
                                    End If

                                    'Create contact
                                    objContactID = _objCRM.GetCrmService.Create(objContact)

                                    LogHelper.Output("Contact '{0}' created.", objName.FirstName & " " & objName.LastName)

                                    'Create a generic representation of the type used for parsing etc
                                    objIncomingEnt = New IncomingEntity
                                    objIncomingEnt.FirstName = objContact.firstname
                                    objIncomingEnt.LastName = objContact.lastname
                                    objIncomingEnt.ID = objContactID
                                    objIncomingEnt.Type = EntityName.contact.ToString

                                    'Check to see if parent account options are alright
                                    If Not SetCaseCustomer(objContact, objIncomingEnt, QueueSettings) Then
                                        objIncomingEnt.CaseEntityID = objContactID
                                        objIncomingEnt.CaseEntityType = EntityName.contact.ToString
                                    End If

                                    'Create the case
                                    CreateCase(objIncomingEnt, objEmail, QueueSettings, objQueue, objQueueOwner)

                                End If
                            End If
                        End If
                    End If
                End If
            Next

        Catch ex As Exception
            LogHelper.Log("Unhandled exception occurred in ProcessQueue", ex, LogHelper.LogType.Exception)
        End Try

    End Sub

    Private Sub DetachFromQueue(ByVal objEmail As email, ByVal strQueueID As String)

        Dim objQueueQuery As QueryExpressionHelper
        Dim objDetachRequest As DetachFromQueueEmailRequest

        'Loop through and remove e-mail from queues 
        objQueueQuery = New QueryExpressionHelper(EntityName.queueitem)
        objQueueQuery.Columns.AddColumns("queueitemid", "queueid")
        objQueueQuery.Criteria.Conditions.AddCondition("objecttypecode", ConditionOperator.Equal, 4202)
        objQueueQuery.Criteria.Conditions.AddCondition("objectid", ConditionOperator.Equal, objEmail.activityid.Value.ToString)

        If Not String.IsNullOrEmpty(strQueueID) Then
            objQueueQuery.Criteria.Conditions.AddCondition("queueid", ConditionOperator.Equal, strQueueID)
        End If

        'Detach from queues
        For Each objQItem As queueitem In _objCRM.GetCrmService.RetrieveMultiple(objQueueQuery.Query).BusinessEntities
            objDetachRequest = New DetachFromQueueEmailRequest
            objDetachRequest.EmailId = objEmail.activityid.Value
            objDetachRequest.QueueId = objQItem.queueid.Value

            _objCRM.GetCrmService.Execute(objDetachRequest)
        Next

    End Sub

    ''' <summary>
    ''' Checks the occurrance of words in the subject line.
    ''' </summary>
    ''' <param name="objEmail">The e-mail to check.</param>
    ''' <param name="QueueSettings">The queue settings</param>
    Private Function MatchIgnoredWords(ByVal objEmail As email, ByVal QueueSettings As QueueSetting) As email

        For Each strWord As String In QueueSettings.IgnoreList.Split(";"c)

            'The if the subject contains any of the words
            If Not String.IsNullOrEmpty(objEmail.subject) AndAlso objEmail.subject.ToLower.Contains(strWord.ToLower) Then
                'Move the e-mail to the temp queue?
                If QueueSettings.MoveIgnored Then
                    _objCRM.RouteEMail(objEmail, QueueSettings.QueueID, QueueSettings.TempQueueID)
                End If
                Return Nothing
            End If
        Next

        Return objEmail

    End Function

    ''' <summary>
    ''' Make sure we're the To line and not CC or Bcc
    ''' </summary>
    ''' <param name="objEMail">The email to check</param>
    ''' <param name="objQueue">The queue</param>
    ''' <param name="QueueSettings">The settings</param>
    Private Function MatchAllowOnlyTo(ByVal objEMail As email, ByVal objQueue As queue, ByVal QueueSettings As QueueSetting) As email

        Dim blnMatch As Boolean

        'Loop through the to array and check that we're in it
        For Each objParty As activityparty In objEMail.to
            If objParty.addressused.ToLower = objQueue.emailaddress.ToLower Then
                blnMatch = True
                Exit For
            End If
        Next

        'If we are not then move the mail to the temp queue and, optionally, send an e-mail
        If blnMatch Then
            Return objEMail
        Else
            LogHelper.Output("We are not in the 'to' field, ignoring e-mail.")

            _objCRM.RouteEMail(objEMail, QueueSettings.QueueID, QueueSettings.TempQueueID)

            'Should we notify the sender?
            If QueueSettings.AllowOnlyToReply Then
                Try
                    _objCRM.SendNetMail(QueueSettings.AllowOnlyToSubject, _
                                        IO.File.ReadAllText(QueueSettings.AllowOnlyToTemplate).Replace("{queueaddress}", objQueue.emailaddress).Replace("{subject}", objEMail.subject), _
                                        objEMail.from(0).addressused, _
                                        QueueSettings.AllowOnlyToNotify, _
                                        QueueSettings.AllowOnlyToFrom, _
                                        QueueSettings.AllowOnlyToUseHtml)
                Catch ex As Exception
                    LogHelper.Log("Error sending 'AllowOnlyTo' mail.", ex, LogHelper.LogType.Exception)
                End Try
            End If

            'Return nothing so we don't carry on
            Return Nothing
        End If

    End Function

    ''' <summary>
    ''' Creates a case
    ''' </summary>
    ''' <param name="objEntity">The customer to create the case for</param>
    ''' <param name="objEMail">The e-mail to relate</param>
    ''' <param name="QueueSettings">Instance of queue settings</param>
    ''' <param name="objQueue">The queue</param>
    Private Function CreateCase(ByVal objEntity As IncomingEntity, ByVal objEMail As email, ByVal QueueSettings As QueueSetting, ByVal objQueue As queue, ByVal objQueueOwner As systemuser) As DynamicEntity

        Dim objCase As New DynamicEntity
        Dim objCaseID As Guid
        Dim objQueueQuery As QueryExpressionHelper
        Dim objSourceQueue As Guid
        Dim objEntities As BusinessEntityCollection
        Dim strSubject As String
        Dim strBody As String
        Dim objCreate As TargetCreateDynamic
        Dim objRequest As CreateRequest
        Dim objResponse As CreateResponse
        Dim objProperties As New ArrayList
        Dim objAtt As CRMMeta.AttributeMetadata

        'Go on and create case
        LogHelper.Output("Creating case...")

        'Type is incident
        objCase.Name = EntityName.incident.ToString

        'The title should be the subject of the e-mail
        Dim objTitle As New StringProperty
        objTitle.Name = "title"
        objTitle.Value = objEMail.subject
        objProperties.Add(objTitle)

        'Set the customer
        Dim objCustomer = New CustomerProperty
        objCustomer.Name = "customerid"
        objCustomer.Value = New Customer
        objCustomer.Value.type = objEntity.CaseEntityType
        objCustomer.Value.Value = objEntity.CaseEntityID
        objProperties.Add(objCustomer)

        'Set the owner
        Dim objOwner As New OwnerProperty
        objOwner.Name = "ownerid"
        objOwner.Value = New Owner
        objOwner.Value.type = EntityName.systemuser.ToString
        objOwner.Value.Value = objQueue.primaryuserid.Value
        objProperties.Add(objOwner)

        'Should we update the responsible contact field?
        If QueueSettings.SetResponsibleContact AndAlso objEntity.CaseEntityType = EntityName.contact.ToString Then
            Dim objResponsible As New LookupProperty
            objResponsible.Name = "responsiblecontactid"
            objResponsible.Value = New Lookup
            objResponsible.Value.type = objEntity.CaseEntityType
            objResponsible.Value.Value = objEntity.CaseEntityID
            objProperties.Add(objResponsible)
        End If

        'Should the description be updated?
        If QueueSettings.UpdateCaseDescription Then
            'A lame attempt to remove html formatting and preserve some air in the text...
            Dim objDesc As New StringProperty
            objDesc.Name = "description"
            objDesc.Value = _objCRM.CleanHTML(objEMail.description)

            'Limit length
            objAtt = _objCRM.GetAttribute(EntityName.incident.ToString, "description")

            If objDesc.Value.Length > CType(objAtt, CRMMeta.MemoAttributeMetadata).MaxLength.Value - 1 Then
                objDesc.Value = objDesc.Value.Substring(0, CType(objAtt, CRMMeta.MemoAttributeMetadata).MaxLength.Value - 1)
            End If

            objProperties.Add(objDesc)
        End If

        'Set properties
        objCase.Properties = CType(objProperties.ToArray(GetType([Property])), [Property]())

        'Update dynamic properties
        SetDynamicProperties(objCase, QueueSettings)

        'Create the target.
        objCreate = New TargetCreateDynamic
        objCreate.Entity = objCase

        'Create the request object.
        objRequest = New CreateRequest

        'Set the properties of the request object.
        objRequest.Target = objCreate

        'Execute the request.
        objResponse = CType(_objCRM.GetCrmService.Execute(objRequest), CreateResponse)

        'Create the case...
        objCaseID = objResponse.id

        '...and retrieve it to get poplulated defaults or result of workflows etc
        objCase = _objCRM.GetDynamicEntity(EntityName.incident.ToString, objCaseID.ToString)

        LogHelper.Output("Case {0} created.", objCase.GetValue("ticketnumber"))

        'The case is by default placed in the Work in progress queue for the owner of the case.
        'Let's retrieve that queue
        objQueueQuery = New QueryExpressionHelper(EntityName.queue)
        objQueueQuery.Columns.AddColumn("queueid")
        objQueueQuery.Criteria.Conditions.AddCondition("primaryuserid", ConditionOperator.Equal, CType(objCase("ownerid"), Owner).Value.ToString)
        objQueueQuery.Criteria.Conditions.AddCondition("queuetypecode", ConditionOperator.Equal, 3) '3 is WIP queue type

        'Execute the query
        objEntities = _objCRM.GetCrmService.RetrieveMultiple(objQueueQuery.Query)

        'There should only be one queue...
        If objEntities.BusinessEntities.Length > 0 Then
            objSourceQueue = CType(objEntities.BusinessEntities(0), queue).queueid.Value

            'Route the case to the correct queue
            _objCRM.RouteCase(objCaseID, objSourceQueue.ToString, QueueSettings.QueueID)

            'Update the e-mail and set "regarding" to the case
            objEMail.regardingobjectid = New Lookup
            objEMail.regardingobjectid.type = EntityName.incident.ToString
            objEMail.regardingobjectid.Value = objCaseID

            'Update email
            With objEMail.from(0)
                .partyid = New Lookup
                .partyid.type = objEntity.Type
                .partyid.Value = objEntity.ID
            End With

            'Update e-mail
            _objCRM.GetCrmService.Update(objEMail)

            'Detach the mail from ALL queues 
            DetachFromQueue(objEMail, Nothing)

            'Send an e-mail
            If QueueSettings.SendCaseMail Then
                LogHelper.Output("Replying sender...")

                'Parse the text
                strSubject = ParseCaseMail(QueueSettings.CaseMailSubject, objCase, objEntity, objEMail, objQueue)
                strBody = ParseCaseMail(File.ReadAllText(QueueSettings.CaseMailTemplate), objCase, objEntity, objEMail, objQueue)

                'If the setting casemail-from is a mail addrees then send using Net mail...
                If QueueSettings.CaseMailFrom.Contains("@") OrElse QueueSettings.CaseMailFrom.Contains("*") Then
                    _objCRM.SendNetMail(strSubject, _
                                        strBody, _
                                        objEMail.from(0).addressused, _
                                        QueueSettings.CaseMailNotify, _
                                        CType(IIf(QueueSettings.CaseMailFrom = "*", objQueueOwner.internalemailaddress, QueueSettings.CaseMailFrom), String), _
                                        QueueSettings.CaseMailUseHtml)
                Else
                    '...otherwise, send using CRM.
                    _objCRM.SendCrmMail(strSubject, _
                                        strBody, _
                                        objEntity, _
                                        QueueSettings.CaseMailFrom, _
                                        QueueSettings.CaseMailFromType, _
                                        objCase)

                    'Send a notification that a case has been created?
                    If Not String.IsNullOrEmpty(QueueSettings.CaseMailNotify) Then
                        _objCRM.SendNetMail(strSubject, _
                        strBody, _
                        QueueSettings.CaseMailNotify, _
                        Nothing, _
                        CType(IIf(QueueSettings.VerificationMailFrom = "*", objQueueOwner.internalemailaddress, QueueSettings.VerificationMailFrom), String), _
                        QueueSettings.CaseMailUseHtml)
                    End If
                End If

                LogHelper.Output("e-mail sent.")
            End If

            'Send a separate notification?
            If QueueSettings.SendCaseNotification Then
                LogHelper.Output("Sending notification...")

                'Parse the text
                strSubject = ParseCaseMail(QueueSettings.CaseNotificationSubject, objCase, objEntity, objEMail, objQueue)
                strBody = ParseCaseMail(File.ReadAllText(QueueSettings.CaseNotificationTemplate), objCase, objEntity, objEMail, objQueue)

                'Send the notification mail
                _objCRM.SendNetMail(strSubject, _
                                    strBody, _
                                    CType(IIf(QueueSettings.CaseNotificationTo = "*", objQueueOwner.internalemailaddress, QueueSettings.CaseNotificationTo), String), _
                                    "", _
                                    CType(IIf(QueueSettings.CaseNotificationFrom = "*", objQueueOwner.internalemailaddress, QueueSettings.CaseNotificationFrom), String), _
                                    QueueSettings.CaseNotificationUseHtml)

                LogHelper.Output("Notification sent.")

            End If
        End If

        Return objCase

    End Function

    ''' <summary>
    ''' Updates properties.
    ''' </summary>
    ''' <param name="objCase"></param>
    ''' <param name="QueueSettings"></param>
    ''' <remarks></remarks>
    Private Sub SetDynamicProperties(ByVal objCase As DynamicEntity, ByVal QueueSettings As QueueSetting)

        Dim objProp As [Property]
        Dim strSplit() As String
        Dim objProperties As New ArrayList(objCase.Properties)
        Dim objAtt As CRMMeta.AttributeMetadata

        Try

            'Loop through all properties in app.config
            For Each strProperty In QueueSettings.CaseAttributes.Split("|"c)

                If strProperty.Trim.Length = 0 Then Exit Sub

                'Split on delimiter
                strSplit = strProperty.Split("#"c)
                objProp = Nothing

                If strSplit.Length <> 2 Then
                    LogHelper.Log(String.Format("Dynamic property definition incorrect: {0}", strProperty), Nothing, LogHelper.LogType.Warning)
                Else

                    'Get the attribute metadata
                    objAtt = _objCRM.GetAttribute(EntityName.incident.ToString, strSplit(0))

                    'Create the property
                    Select Case objAtt.AttributeType.Value
                        Case CRMMeta.AttributeType.String, CRMMeta.AttributeType.Memo
                            objProp = New StringProperty
                            DirectCast(objProp, StringProperty).Name = strSplit(0)
                            DirectCast(objProp, StringProperty).Value = strSplit(1)
                        Case CRMMeta.AttributeType.Integer
                            objProp = New CrmNumberProperty
                            DirectCast(objProp, CrmNumberProperty).Name = strSplit(0)
                            DirectCast(objProp, CrmNumberProperty).Value = New CrmNumber
                            DirectCast(objProp, CrmNumberProperty).Value.Value = Integer.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.Float
                            objProp = New CrmFloatProperty
                            DirectCast(objProp, CrmFloatProperty).Name = strSplit(0)
                            DirectCast(objProp, CrmFloatProperty).Value = New CrmFloat
                            DirectCast(objProp, CrmFloatProperty).Value.Value = Double.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.Decimal
                            objProp = New CrmDecimalProperty
                            DirectCast(objProp, CrmDecimalProperty).Name = strSplit(0)
                            DirectCast(objProp, CrmDecimalProperty).Value = New CrmDecimal
                            DirectCast(objProp, CrmDecimalProperty).Value.Value = Decimal.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.DateTime
                            objProp = New CrmDateTimeProperty
                            DirectCast(objProp, CrmDateTimeProperty).Name = strSplit(0)
                            DirectCast(objProp, CrmDateTimeProperty).Value = New CrmDateTime
                            DirectCast(objProp, CrmDateTimeProperty).Value.Value = strSplit(1)
                        Case CRMMeta.AttributeType.Customer
                            objProp = New CustomerProperty
                            DirectCast(objProp, CustomerProperty).Name = strSplit(0)
                            DirectCast(objProp, CustomerProperty).Value = New Customer
                            DirectCast(objProp, CustomerProperty).Value.Value = New Guid(strSplit(1))
                        Case CRMMeta.AttributeType.Boolean
                            objProp = New CrmBooleanProperty
                            DirectCast(objProp, CrmBooleanProperty).Name = strSplit(0)
                            DirectCast(objProp, CrmBooleanProperty).Value = New CrmBoolean
                            DirectCast(objProp, CrmBooleanProperty).Value.Value = Boolean.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.Status
                            objProp = New StatusProperty
                            DirectCast(objProp, StatusProperty).Name = strSplit(0)
                            DirectCast(objProp, StatusProperty).Value = New Status
                            DirectCast(objProp, StatusProperty).Value.Value = Integer.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.State
                            objProp = New StateProperty
                            DirectCast(objProp, StateProperty).Name = strSplit(0)
                            DirectCast(objProp, StateProperty).Value = strSplit(1)
                        Case CRMMeta.AttributeType.Picklist
                            objProp = New PicklistProperty
                            DirectCast(objProp, PicklistProperty).Name = strSplit(0)
                            DirectCast(objProp, PicklistProperty).Value = New Picklist
                            DirectCast(objProp, PicklistProperty).Value.Value = Integer.Parse(strSplit(1))
                        Case CRMMeta.AttributeType.Lookup
                            objProp = New LookupProperty
                            DirectCast(objProp, LookupProperty).Name = strSplit(0)
                            DirectCast(objProp, LookupProperty).Value = New Lookup
                            DirectCast(objProp, LookupProperty).Value.Value = New Guid(strSplit(1))

                        Case Else
                            LogHelper.Log(String.Format("Dynamic property definition incorrect: {0}", strProperty), Nothing, LogHelper.LogType.Warning)

                    End Select

                    If objProp IsNot Nothing Then

                        'Add properties to ArrayList.
                        objProperties.Add(objProp)
                    End If
                End If

            Next
        Catch ex As Exception
            LogHelper.Log("Error setting dynamic properties.", ex, LogHelper.LogType.Exception)
            Exit Sub
        End Try

        'Update the properties array on the DynamicEntity.
        objCase.Properties = CType(objProperties.ToArray(GetType([Property])), [Property]())

    End Sub

    ''' <summary>
    ''' Determines the case customer (either contact or parent account)
    ''' </summary>
    ''' <param name="objContact">The contact</param>
    ''' <param name="objIncomingEnt">The entity to update</param>
    ''' <param name="QueueSettings">Settings</param>
    ''' <returns>True if OK, otherwise false (bail out)</returns>
    Private Function SetCaseCustomer(ByVal objContact As contact, ByVal objIncomingEnt As IncomingEntity, ByVal QueueSettings As QueueSetting) As Boolean

        'Check if the option is set
        If QueueSettings.MapCaseToAccount Then

            'Check to see if the contact has a parent account
            If objContact.parentcustomerid IsNot Nothing Then
                'Then use the parent account
                objIncomingEnt.CaseEntityID = objContact.parentcustomerid.Value
                objIncomingEnt.CaseEntityType = objContact.parentcustomerid.type
            Else
                'Should we assign it to the contact if the parent account is missing?
                If QueueSettings.ParentAccountMissing Then
                    objIncomingEnt.CaseEntityID = objIncomingEnt.ID
                    objIncomingEnt.CaseEntityType = objIncomingEnt.Type
                Else
                    'Bail out (no parent account).
                    Return False
                End If
            End If
        Else
            objIncomingEnt.CaseEntityID = objIncomingEnt.ID
            objIncomingEnt.CaseEntityType = objIncomingEnt.Type
        End If

        Return True

    End Function

    ''' <summary>
    ''' Clears the temp queue according to config settings
    ''' </summary>
    ''' <param name="QueueSettings">The queue settings to use.</param>
    Public Sub ProcessTempQueue(ByVal QueueSettings As QueueSetting)

        Dim objQueueItems As New QueryExpressionHelper(EntityName.queueitem)
        Dim objQueueResult As BusinessEntity()
        Dim objDetachRequest As DetachFromQueueEmailRequest

        'Exit if 0
        If QueueSettings.TempQueueAge < 1 Then
            Exit Sub
        End If

        objQueueItems.Columns.AddColumns("objectid", "queueitemid")

        'Filter on the tempqueue id and object type 4202 (EntityName.email)
        objQueueItems.Criteria.FilterOperator = LogicalOperator.And
        objQueueItems.Criteria.Conditions.AddCondition("queueid", ConditionOperator.Equal, QueueSettings.TempQueueID)
        objQueueItems.Criteria.Conditions.AddCondition("objecttypecode", ConditionOperator.Equal, 4202)
        objQueueItems.Criteria.Conditions.AddCondition("enteredon", ConditionOperator.LessThan, DateTime.UtcNow.AddHours(QueueSettings.TempQueueAge - (QueueSettings.TempQueueAge * 2)).ToString("yyyy-MM-ddTHH:mm:ss"))

        LogHelper.Output("Querying temp queue for items to remove...")

        'Execute the query
        Try
            objQueueResult = _objCRM.GetCrmService.RetrieveMultiple(objQueueItems.Query).BusinessEntities
        Catch ex As Exception
            LogHelper.Log("Error querying temp queue for removal.", ex, LogHelper.LogType.Exception)
            Exit Sub
        End Try

        LogHelper.Output("Found {0} e-mail on queue.", objQueueResult.Length.ToString)

        'Loop through result
        For Each objItem As queueitem In objQueueResult
            Try
                'Detach e-mail
                objDetachRequest = New DetachFromQueueEmailRequest
                objDetachRequest.EmailId = objItem.objectid.Value
                objDetachRequest.QueueId = New Guid(QueueSettings.TempQueueID)

                _objCRM.GetCrmService.Execute(objDetachRequest)

                'Remove e-mail
                _objCRM.GetCrmService.Delete(EntityName.email.ToString, objItem.objectid.Value)
            Catch ex As Exception
                LogHelper.Log("Unable to remove item from temp queue.", ex, LogHelper.LogType.Exception)
            End Try
        Next

    End Sub

    ''' <summary>
    ''' Matches ticket number in subject line
    ''' </summary>
    ''' <param name="objEMail">Return nothing if match</param>
    Private Function MatchCaseString(ByVal objEMail As email) As email

        Dim objCaseQuery As QueryExpressionHelper
        Dim objResult() As BusinessEntity

        If _objRegEx Is Nothing Then
            Try
                'Create reg exp that will match "[casesuffix]-[n digits]-[alpha uniquespecifier]" from org settings. Can't use String.Format here...
                _objRegEx = New Regex("\b(0)\b-\d+-\b\w{(1)}\b".Replace("(0)", _objCRM.GetOrgInfo.caseprefix).Replace("(1)", _objCRM.GetOrgInfo.uniquespecifierlength.Value.ToString), RegexOptions.IgnoreCase)
            Catch ex As Exception
                LogHelper.Log("Error constructing regexp.", ex, LogHelper.LogType.Exception)
            End Try
        End If

        'Right, we found a potential case number in the title, let see if we can find a match
        If _objRegEx IsNot Nothing AndAlso Not String.IsNullOrEmpty(objEMail.subject) AndAlso _objRegEx.IsMatch(objEMail.subject) Then
            objCaseQuery = New QueryExpressionHelper(EntityName.incident.ToString)

            'All we need is the incident id
            objCaseQuery.Columns.AddColumns("incidentid")
            objCaseQuery.Criteria.FilterOperator = LogicalOperator.And
            objCaseQuery.Criteria.Conditions.AddCondition("ticketnumber", ConditionOperator.Equal, _objRegEx.Match(objEMail.subject).Value)

            'Execute the query
            Try
                objResult = _objCRM.GetCrmService.RetrieveMultiple(objCaseQuery.Query).BusinessEntities
            Catch ex As Exception
                LogHelper.Log("Error fetching case by subject.", ex, LogHelper.LogType.Exception)
                'Carry on
            End Try

            'Return the case if we got a match
            If objResult IsNot Nothing AndAlso objResult.Length > 0 Then
                objEMail.regardingobjectid = New Lookup
                objEMail.regardingobjectid.type = EntityName.incident.ToString
                objEMail.regardingobjectid.Value = CType(objResult(0), incident).incidentid.Value

                Try
                    _objCRM.GetCrmService.Update(objEMail)

                    'Return nothing so we don't carry on.
                    Return Nothing

                Catch ex As Exception
                    LogHelper.Log("Error updating e-mail when case subject matching.", ex, LogHelper.LogType.Exception)
                End Try
            End If

        End If

        Return objEMail

    End Function

    ''' <summary>
    ''' Replaces information in the mail to be sent
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ParseCaseMail(ByVal strText As String, _
                                   ByVal objCase As DynamicEntity, _
                                   ByVal objEntity As IncomingEntity, _
                                   ByVal objEmail As email, _
                                   ByVal objQueue As queue) As String

        Dim strParsedText As String

        'Parse and replace information used in the e-mail. First the old way for 
        'backward compatibility...
        strParsedText = strText.Replace("{ticketnumber}", objCase.GetValue("ticketnumber")) _
                               .Replace("{title}", objCase.GetValue("title")) _
                               .Replace("{firstname}", objEntity.Firstname) _
                               .Replace("{lastname}", objEntity.Lastname) _
                               .Replace("{sender}", objEmail.from(0).addressused) _
                               .Replace("{caseid}", objCase.GetValue("incidentid").ToLower) _
                               .Replace("{queuename}", objQueue.name) _
                               .Replace("{emailbodytext}", _objCRM.CleanHTML(objEmail.description)) _
                               .Replace("{emailbodyhtml}", _objCRM.CleanHTML(objEmail.description).Replace(Chr(13), "<br/>")) _
                               .Replace("{date}", Now.ToShortDateString) _
                               .Replace("{time}", Now.ToShortTimeString) _
                               .Replace("{emailid}", objEmail.activityid.Value.ToString.ToLower)

        '...and then using dynamic entity info
        If _objParser Is Nothing Then
            _objParser = New Regex("{#\S*#}", RegexOptions.IgnoreCase)
        End If

        'Loop through all matches
        For Each objMatch As Match In _objParser.Matches(strParsedText)
            strParsedText = strParsedText.Replace(objMatch.Value, objCase.GetValue(objMatch.Value.Replace("{#", "").Replace("#}", "")))
        Next

        Return strParsedText

    End Function


End Class
